/**
 * xmlloader.cpp is part of Gedule.
 *
 * Gedule is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Gedule is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Gedule.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "xmlloader.h"
#include <QFile>
#include "../../gedulemgr.h"
#include "../../definitions.h"
#include "../../model/model.h"
#include "../../model/task.h"
#include "../../model/resource.h"
#include "../../model/constraints/model_contraints/allowidles.h"
#include "../../model/constraints/model_contraints/maxtime.h"
#include "../../model/constraints/model_contraints/precedence.h"
#include "../../model/constraints/task_contraints/lenght.h"
#include "../../model/constraints/task_contraints/neededresources.h"
#include "../../model/constraints/task_contraints/timewindow.h"
#include "../../model/constraints/resource_contraints/capacity.h"
#include "../../model/constraints/resource_contraints/unavailabilities.h"
#include "../../model/factory/modelconstraintfactory.h"
#include "../../model/factory/taskfactory.h"
#include "../../model/factory/taskconstraintfactory.h"
#include "../../model/factory/resourcefactory.h"
#include "../../model/factory/resourceconstraintfactory.h"
#include "../../utils/logging/logmanager.h"

///////////////////////////////////////////////////////////////
XmlLoader::XmlLoader( const std::string & a_file ) :
    Loader(),
    m_file( a_file ),
    m_xmlStream()
{
}

///////////////////////////////////////////////////////////////
bool XmlLoader::Load( Model & a_model )
{
    bool ret = false;

    std::ostringstream strStream;
    strStream << "---- Loading XML file: " << m_file << " ----";
    GET_LOG_MGR().Log( LOG_LOGGER, strStream.str() );
    strStream.str( std::string() );

    QFile file( QString( m_file.c_str() ) );
    if( file.open( QFile::ReadOnly | QFile::Text ) )
    {
        m_xmlStream.setDevice( &file );

        while( !m_xmlStream.atEnd() )
        {
            ReadElement( m_xmlStream.readNext(), m_xmlStream, a_model );
        }

        if( m_xmlStream.hasError() )
        {
            HandleError( m_xmlStream );
            ret = false;
        }
        else
            ret = true;

        strStream << "---- XML file: " << m_file << " loaded ----";
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str() );
        strStream.str( std::string() );
    }
    else
    {
        strStream << "Cannot open file " << m_file;
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), ERROR_LOG );
        strStream.str( std::string() );
    }

    m_xmlStream.clear();
    file.close();

    return ret;
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadElement( QXmlStreamReader::TokenType a_type, QXmlStreamReader & a_xmlStream, Model & a_model )
{
    if( IsStartElement( a_type ) )
    {
        std::string elementName = a_xmlStream.name().toString().toStdString() ;
        if( elementName == MODEL_TAG )
            ReadModel( a_xmlStream, a_model );
        if( elementName == TASK_TAG )
            ReadTask( a_xmlStream, a_model   );
        else if( elementName == RESOURCE_TAG )
            ReadResource( a_xmlStream, a_model );
        else if( elementName == CONSTRAINT_TAG )
            ReadModelConstraint( a_xmlStream, a_model );
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadModel(QXmlStreamReader & a_xmlStream, Model & a_model )
{
    QXmlStreamAttributes attributes = a_xmlStream.attributes();
    if( attributes.hasAttribute( ID_ATTRIBUTE.c_str() ) )
    {
        std::string modelId = attributes.value( ID_ATTRIBUTE.c_str() ).toString().toStdString();
        a_model.Clear();
        a_model.ModelName( modelId );
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadTask( QXmlStreamReader & a_xmlStream, Model & a_model )
{
    Task * newTask = 0;

    QXmlStreamAttributes attributes = a_xmlStream.attributes();
    if( attributes.hasAttribute( ID_ATTRIBUTE.c_str() ) )
    {
        std::string taskId = attributes.value( ID_ATTRIBUTE.c_str() ).toString().toStdString();
        newTask = GET_TASK_FACTORY().CreateTask( taskId );
    }

    if( newTask )
    {
        std::ostringstream strStream;
        strStream << "Reading Task: " << newTask->TaskName();
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str() );

        ReadTaskConstraints( a_xmlStream, newTask );
        a_model.Add( newTask );
    }
    else if( newTask )
    {
        delete newTask;
    }

    newTask = 0;
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadTaskConstraints( QXmlStreamReader & a_xmlStream, Task * a_task )
{
    bool end = false;
    while( !end && !a_xmlStream.atEnd() )
    {
        QXmlStreamReader::TokenType tokenType = a_xmlStream.readNext();
        if( IsStartElement( tokenType ) )
        {
            std::string elementName = a_xmlStream.name().toString().toStdString() ;
            if( elementName == CONSTRAINT_TAG )
                ReadTaskConstraint( a_xmlStream, a_task );
            else
            {
                std::ostringstream strStream;
                strStream << TASK_TAG << " has unknown element : " << elementName;
                GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
            }
        }
        else if( IsEndElement( tokenType ) )
        {
            std::string elementName = a_xmlStream.name().toString().toStdString() ;
            end = elementName == TASK_TAG;
        }
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadTaskConstraint( QXmlStreamReader & a_xmlStream, Task * a_task )
{
    QXmlStreamAttributes xmlAttributes = a_xmlStream.attributes();
    if( xmlAttributes.hasAttribute( TYPE_ATTRIBUTE.c_str() ) )
    {
        std::string constraintType = xmlAttributes.value( TYPE_ATTRIBUTE.c_str() ).toString().toStdString();
        if( constraintType == TIME_WINDOW_CONSTRAINT_TYPE )
            ReadTimeWindowConstraint( a_xmlStream, a_task );
        else if( constraintType == LENGHT_CONSTRAINT_TYPE )
            ReadLenghtConstraint( a_xmlStream, a_task );
        else if( constraintType == RESOURCES_NEEDED_CONSTRAINT_TYPE )
            ReadResourcesNeededConstraint( a_xmlStream, a_task );
        else
        {
            std::ostringstream strStream;
            strStream << "Constraint type unknown: " << constraintType;
            GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
        }
    }
    else
    {
        std::ostringstream strStream;
        strStream << "Task constraint without type";
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadTimeWindowConstraint( QXmlStreamReader & a_xmlStream, Task * a_task )
{
    QXmlStreamAttributes xmlAttributes = a_xmlStream.attributes();
    bool hasStart = xmlAttributes.hasAttribute( START_ATTRIBUTE.c_str() );
    bool hasEnd   = xmlAttributes.hasAttribute( END_ATTRIBUTE.c_str() );
    if( a_task && hasStart && hasEnd )
    {
        tTime start = xmlAttributes.value( START_ATTRIBUTE.c_str() ).toString().toUShort();
        tTime end   = xmlAttributes.value( END_ATTRIBUTE.c_str() ).toString().toUShort();
        a_task->Add( GET_TASK_CONSTRAINT_FACTORY().CreateTaskTimeWindowConstraint( start, end ) );
    }
    else
    {
        std::ostringstream strStream;
        strStream << "Malformed TaskTimeWindowConstraint";
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadLenghtConstraint( QXmlStreamReader & a_xmlStream, Task * a_task )
{
    QXmlStreamAttributes xmlAttributes = a_xmlStream.attributes();
    bool hasLenght = xmlAttributes.hasAttribute( LENGHT_ATTRIBUTE.c_str() );
    if( a_task && hasLenght )
    {
        tTime lenght = xmlAttributes.value( LENGHT_ATTRIBUTE.c_str() ).toString().toUShort();
        a_task->Add( GET_TASK_CONSTRAINT_FACTORY().CreateTaskLenghtConstraint( lenght ) );
    }
    else
    {
        std::ostringstream strStream;
        strStream << "Malformed TaskLenghtConstraint";
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadResourcesNeededConstraint( QXmlStreamReader & a_xmlStream, Task * a_task )
{
    QXmlStreamAttributes xmlAttributes = a_xmlStream.attributes();
    bool hasLenght = xmlAttributes.hasAttribute( RESOURCES_NEEDED_ATTRIBUTE.c_str() );
    if( a_task && hasLenght )
    {
        tNeededResources neededResources = xmlAttributes.value( RESOURCES_NEEDED_ATTRIBUTE.c_str() ).toString().toUShort();
        a_task->Add( GET_TASK_CONSTRAINT_FACTORY().CreateTaskResourcesNeededConstraint( neededResources ) );
    }
    else
    {
        std::ostringstream strStream;
        strStream << "Malformed TaskResourcesNeededConstraint";
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadResource( QXmlStreamReader & a_xmlStream, Model & a_model )
{
    Resource * newResource = 0;

    QXmlStreamAttributes attributes = a_xmlStream.attributes();
    if( attributes.hasAttribute( ID_ATTRIBUTE.c_str() ) )
    {
        std::string resourceId = attributes.value( ID_ATTRIBUTE.c_str() ).toString().toStdString();
        newResource = GET_RESOURCE_FACTORY().CreateResource( resourceId );
    }

    if( newResource )
    {
        std::ostringstream strStream;
        strStream << "Reading Resource : " << newResource->ResourceName();
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str() );

        ReadResourceConstraints( a_xmlStream, newResource );
        a_model.Add( newResource );
    }
    else if( newResource )
    {
        delete newResource;
    }

    newResource = 0;
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadResourceConstraints( QXmlStreamReader & a_xmlStream, Resource * a_resource )
{
    bool end = false;
    while( !end && !a_xmlStream.atEnd() )
    {
        QXmlStreamReader::TokenType tokenType = a_xmlStream.readNext();
        if( IsStartElement( tokenType ) )
        {
            std::string elementName = a_xmlStream.name().toString().toStdString() ;
            if( elementName == CONSTRAINT_TAG )
                ReadResourceConstraint( a_xmlStream, a_resource );
            else
            {
                std::ostringstream strStream;
                strStream << RESOURCE_TAG << " has unknown element : " << elementName;
                GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
            }
        }
        else if( IsEndElement( tokenType ) )
        {
            std::string elementName = a_xmlStream.name().toString().toStdString() ;
            end = elementName == RESOURCE_TAG;
        }
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadResourceConstraint( QXmlStreamReader & a_xmlStream, Resource * a_resource )
{
    QXmlStreamAttributes xmlAttributes = a_xmlStream.attributes();
    if( xmlAttributes.hasAttribute( TYPE_ATTRIBUTE.c_str() ) )
    {
        std::string constraintType = xmlAttributes.value( TYPE_ATTRIBUTE.c_str() ).toString().toStdString();
        if( constraintType == CAPACITY_CONSTRAINT_TYPE )
            ReadCapacityConstraint( a_xmlStream, a_resource );
        else if( constraintType == UNAVAILABILITIES_CONSTRAINT_TYPE )
            ReadUnavailabilitiesConstraint( a_xmlStream, a_resource );
        else
        {
            std::ostringstream strStream;
            strStream << "Constraint type unknown: " << constraintType;
            GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
        }
    }
    else
    {
        std::ostringstream strStream;
        strStream << "Resource constraint without type";
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadCapacityConstraint( QXmlStreamReader & a_xmlStream, Resource * a_resource )
{
    QXmlStreamAttributes xmlAttributes = a_xmlStream.attributes();
    bool hasCapacity = xmlAttributes.hasAttribute( CAPACITY_ATTRIBUTE.c_str() );
    if( a_resource && hasCapacity )
    {
        tCapacity capacity = xmlAttributes.value( CAPACITY_ATTRIBUTE.c_str() ).toString().toUShort();
        a_resource->Add( GET_RESOURCE_CONSTRAINT_FACTORY().CreateResourceCapacityConstraint( capacity ) );
    }
    else
    {
        std::ostringstream strStream;
        strStream << "Malformed TaskResourceCapacityConstraint";
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadUnavailabilitiesConstraint( QXmlStreamReader & a_xmlStream, Resource * a_resource )
{
    Unavailabilities * unavailabilityPeriods = 0;
    bool end = false;
    while( !end && !a_xmlStream.atEnd() )
    {
        QXmlStreamReader::TokenType tokenType = a_xmlStream.readNext();
        if( IsStartElement( tokenType ) )
        {
            std::string elementName = a_xmlStream.name().toString().toStdString() ;
            if( elementName == UNAVAILABILITY_TAG )
            {
                if( !unavailabilityPeriods )
                    unavailabilityPeriods = GET_RESOURCE_CONSTRAINT_FACTORY().CreateResourceUnavailabilityPeriodsConstraint();

                ReadUnavailability( a_xmlStream, unavailabilityPeriods );
            }
            else
            {
                std::ostringstream strStream;
                strStream << CONSTRAINT_TAG << " " << UNAVAILABILITIES_CONSTRAINT_TYPE  << " has unknown element : " << elementName;
                GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
            }
        }
        else if( IsEndElement( tokenType ) )
        {
            std::string elementName = a_xmlStream.name().toString().toStdString() ;
            end = elementName == CONSTRAINT_TAG;
        }
    }

    if( a_resource && unavailabilityPeriods && !unavailabilityPeriods->UnavailabilityPeriods().empty() )
    {
        a_resource->Add( unavailabilityPeriods );
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadUnavailability( QXmlStreamReader & a_xmlStream, Unavailabilities * a_constraint )
{
    QXmlStreamAttributes xmlAttributes = a_xmlStream.attributes();
    bool hasStart = xmlAttributes.hasAttribute( START_ATTRIBUTE.c_str() );
    bool hasEnd   = xmlAttributes.hasAttribute( END_ATTRIBUTE.c_str() );
    if( a_constraint && hasStart && hasEnd )
    {
        tTime start = xmlAttributes.value( START_ATTRIBUTE.c_str() ).toString().toUShort();
        tTime end   = xmlAttributes.value( END_ATTRIBUTE.c_str() ).toString().toUShort();
        a_constraint->Add( std::make_pair( start, end ) );
    }
    else
    {
        std::ostringstream strStream;
        strStream << "Malformed Unavailability Period";
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadModelConstraint( QXmlStreamReader & a_xmlStream, Model & a_model )
{
    QXmlStreamAttributes xmlAttributes = a_xmlStream.attributes();
    if( xmlAttributes.hasAttribute( TYPE_ATTRIBUTE.c_str() ) )
    {
        std::string constraintType = xmlAttributes.value( TYPE_ATTRIBUTE.c_str() ).toString().toStdString();
        if( constraintType == ALLOW_IDLE_CONSTRAINT_TYPE )
            ReadAllowIdleConstraint( a_xmlStream, a_model );
        else if( constraintType == MAX_TIME_CONSTRAINT_TYPE )
            ReadMaxTimeConstraint( a_xmlStream, a_model );
        else if( constraintType == PRECEDENCE_CONSTRAINT_TYPE )
            ReadPrecedenceConstraint( a_xmlStream, a_model );
        else
        {
            std::ostringstream strStream;
            strStream << "Constraint type unknown: " << constraintType;
            GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
        }
    }
    else
    {
        std::ostringstream strStream;
        strStream << "Model constraint without type";
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadAllowIdleConstraint( QXmlStreamReader & a_xmlStream, Model & a_model )
{
    QXmlStreamAttributes xmlAttributes = a_xmlStream.attributes();
    bool hasAllowIdle = xmlAttributes.hasAttribute( ALLOW_IDLE_ATTRIBUTE.c_str() );
    if( hasAllowIdle )
    {
        std::string allowIdleStr = xmlAttributes.value( ALLOW_IDLE_ATTRIBUTE.c_str() ).toString().toStdString();
        bool allowIdle = allowIdleStr == TRUE ? true : false;
        a_model.Add( GET_MODEL_CONSTRAINT_FACTORY().CreateAllowIdleConstraint( allowIdle ) );
    }
    else
    {
        std::ostringstream strStream;
        strStream << "Malformed ModelAllowIdleConstraint";
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadMaxTimeConstraint( QXmlStreamReader & a_xmlStream, Model & a_model )
{
    QXmlStreamAttributes xmlAttributes = a_xmlStream.attributes();
    bool hasMaxTime = xmlAttributes.hasAttribute( MAX_TIME_ATTRIBUTE.c_str() );
    if( hasMaxTime )
    {
        tTime maxTime = xmlAttributes.value( MAX_TIME_ATTRIBUTE.c_str() ).toString().toUShort();
        a_model.Add( GET_MODEL_CONSTRAINT_FACTORY().CreateMaxTimeConstraint( maxTime ) );
    }
    else
    {
        std::ostringstream strStream;
        strStream << "Malformed ModelMaxTimeConstraint";
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::ReadPrecedenceConstraint(QXmlStreamReader & a_xmlStream, Model & a_model )
{
    QXmlStreamAttributes xmlAttributes = a_xmlStream.attributes();
    bool hasBefore = xmlAttributes.hasAttribute( BEFORE_ATTRIBUTE.c_str() );
    bool hasAfter  = xmlAttributes.hasAttribute( AFTER_ATTRIBUTE.c_str() );
    if( hasBefore && hasAfter )
    {
        std::string taskBefore = xmlAttributes.value( BEFORE_ATTRIBUTE.c_str() ).toString().toStdString();
        std::string taskAfter  = xmlAttributes.value( AFTER_ATTRIBUTE.c_str() ).toString().toStdString();
        a_model.Add( GET_MODEL_CONSTRAINT_FACTORY().CreatePrecedenceConstraint( taskBefore, taskAfter ) );
    }
    else
    {
        std::ostringstream strStream;
        strStream << "Malformed ModelMaxTimeConstraint";
        GET_LOG_MGR().Log( LOG_LOGGER, strStream.str(), WARN_LOG );
    }
}

///////////////////////////////////////////////////////////////
void XmlLoader::HandleError( QXmlStreamReader & a_xmlStream )
{

}

///////////////////////////////////////////////////////////////
bool XmlLoader::IsStartElement( QXmlStreamReader::TokenType a_type )
{
    return a_type == QXmlStreamReader::StartElement;
}

///////////////////////////////////////////////////////////////
bool XmlLoader::IsEndElement( QXmlStreamReader::TokenType a_type )
{
    return a_type == QXmlStreamReader::EndElement;
}
